package OceanModel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


public interface Integrator {
	// Interface for an Integrator Class
	// Can use to implement a variety of integration schemes.
	void step(double dt);
}

class AdamsBashforthIntegrator implements Integrator {
	  
	// The Adams-Bashforth scheme uses the last 3 model time step derivatives
	// to ensure numerical stability.
	// Has high memory requirement but relatively low computational requirements.
	
	Dynamics navierStokes;
	HashMap<String, GriddedVariable> variables;

	HashMap<String, double[][][]> timestep_1;
	HashMap<String, double[][][]> timestep_2;
	HashMap<String, double[][][]> timestep_3;
	  
	AdamsBashforthIntegrator(Dynamics navierStokes,
	                         HashMap<String, GriddedVariable> variables) {
		this.navierStokes = navierStokes;
		this.variables = variables;
	    
		navierStokes.calculateDerivatives(variables);

		timestep_1 = new HashMap();
		timestep_2 = new HashMap();
		timestep_3 = new HashMap();

		// Initialize model previous time step derivatives to 0
	    for (Map.Entry<String, double[][][]> var : navierStokes.getDerivatives().entrySet()) {		
			String derivativeName = var.getKey();
	      
			double[][][] temparray = var.getValue();

			timestep_2.put(derivativeName, temparray);
			timestep_3.put(derivativeName, temparray);
		}
	    
	} // AdamsBashforthIntegrator

	public void step(double dt) {

		// To step the model forward, first calculate the current model derivatives.
		navierStokes.calculateDerivatives(variables);

		// Iterate over each model derivative
	    for (Map.Entry<String, double[][][]> deriv : navierStokes.getDerivatives().entrySet()) {		
			String derivativeName = deriv.getKey();
			GriddedVariable currentVar = variables.get( derivativeName );

			// Get the model derivatives from the navierStokes object 
			double[][][] ns_deriv = navierStokes.getDerivatives().get( derivativeName );
			
			// Create a new derivative array for the current model timestep
			double[][][] deriv1 = new double[currentVar.nz][currentVar.ny][currentVar.nx];

			timestep_1.put(derivativeName, deriv1);

			double[][][] deriv2 = timestep_2.get( derivativeName );
			double[][][] deriv3 = timestep_3.get( derivativeName );

			// Set the current timestep derivative to the value calculated via the navier-stokes object
			// The update the model variable using the Adams-Bashforth algorithm
			for (int k=0; k < currentVar.nz; k++) {
				for (int j=0; j < currentVar.ny; j++) {
					for (int i=0; i < currentVar.nx; i++) {
						deriv1[k][j][i] = ns_deriv[k][j][i];
						currentVar.data[k][j][i] = currentVar.data[k][j][i] + dt*(23.*deriv1[k][j][i] - 16.*deriv2[k][j][i] + 5.*deriv3[k][j][i])/12.;
					}
				}
			}
	      
			currentVar.swapHalo();
	      
			// Update the stored previous model derivatives for this derivativeName
			timestep_3.put(derivativeName, deriv2);
			timestep_2.put(derivativeName, deriv1);
	      
		} // end for
	        
	}

}
